// Burner Input module
// class version by regret (ref: bsnes)

#include "burner.h"

wchar_t inputSelect[16] = L"DirectInput";
bool inputOkay = false;
static bool getInpOkay;

static InterfaceInfo InpInfo = { NULL, };

// InputInterface
InputInterface inputInterface;

#if defined (_WIN32)
  #include "inp_dinput.cpp"
#endif

const wchar_t* InputInterface::driver_list()
{
	return

#if defined (_WIN32)
	L"DirectInput;"
#endif

  L"None";
}

void InputInterface::driver(const wchar_t* _driver)
{
	if (p) {
		term();
	}

	if (!_driver || !*_driver) {
		p = new Input();
		return;
	}

#if defined (_WIN32)
	else if (!wcscmp(_driver, L"DirectInput")) {
		p = new InputDI();
	}
#endif
	else {
		p = new Input();
	}
}

void InputInterface::term()
{
	if (p) {
		delete p;
		p = 0;
	}
}

int InputInterface::getInpState(int code)
{
	// Return off for keyboard inputs if current input is turned off
	if (code < 0x4000 && !getInpOkay) {
		return 0;
	}
	return p? p->getState(code) : 1;
}

// Read an axis of a joystick
int InputInterface::getInpGamepadAxis(int i, int axis)
{
	return p ? p->readGamepadAxis(i, axis) : 1;
}

// Read an axis of a mouse
int InputInterface::getInpMouseAxis(int i, int axis)
{
	return p ? p->readMouseAxis(i, axis) : 1;
}

// Do one frames worth of keyboard input sliders
int InputInterface::tick()
{
	GameInp* pgi;
	unsigned int i;

	for (i = 0, pgi = gameInput; i < gameInpCount; i++, pgi++) {
		int add = 0;
		if ((pgi->nInput &  GIT_GROUP_SLIDER) == 0) {		// not a slider
			continue;
		}

		if (pgi->nInput == GIT_KEYSLIDER) {
			// Get states of the two keys
			if (getInpState(pgi->Input.Slider.SliderAxis.nSlider[0]))	{
				add -= 0x100;
			}
			if (getInpState(pgi->Input.Slider.SliderAxis.nSlider[1]))	{
				add += 0x100;
			}
		}

		if (pgi->nInput == GIT_JOYSLIDER) {
			// Get state of the axis
			add = getInpGamepadAxis(pgi->Input.Slider.JoyAxis.nJoy, pgi->Input.Slider.JoyAxis.nAxis);
			add /= 0x100;
		}

		// add is now -0x100 to +0x100

		// Change to slider speed
		add *= pgi->Input.Slider.nSliderSpeed;
		add /= 0x100;

		if (pgi->Input.Slider.nSliderCenter) {				// Attact to center
			int v = pgi->Input.Slider.nSliderValue - 0x8000;
			v *= (pgi->Input.Slider.nSliderCenter - 1);
			v /= pgi->Input.Slider.nSliderCenter;
			v += 0x8000;
			pgi->Input.Slider.nSliderValue = v;
		}

		pgi->Input.Slider.nSliderValue += add;
		// Limit slider
		if (pgi->Input.Slider.nSliderValue < 0x0100) {
			pgi->Input.Slider.nSliderValue = 0x0100;
		}
		if (pgi->Input.Slider.nSliderValue > 0xFF00) {
			pgi->Input.Slider.nSliderValue = 0xFF00;
		}
	}
	return 0;
}

int InputInterface::init()
{
	inputOkay = false;

	if (!p) {
		driver(inputSelect);
	}

	if (p->init()) {
		return 1;
	}

	inputOkay = true;
	return 0;
}

int InputInterface::exit()
{
	IntInfoFree(&InpInfo);

	inputOkay = false;

	int ret = 0;
	if (p) {
		ret = p->exit();
	}
	return ret;
}

// This will request exclusive access for mouse and/or request input processing if the application is in the foreground only
//  - bExcusive = 1   - Request exclusive access to inputs (this may apply to only the mouse or all input, depending on the API)
//                      This function will show or hide the mouse cursor as appropriate
//  - foreground = 1 - Request input processing only if application is in foreground (this may not be supported by all APIs)
int InputInterface::setCooperativeLevel(bool exclusive, bool foreground)
{
	if (!inputOkay || !p) {
		return 1;
	}
	return p->setCooperativeLevel(exclusive, foreground);
}

// This will process all PC-side inputs and optionally update the emulated game side.
int InputInterface::make(bool copy)
{
	if (!inputOkay || !p) {
		return 1;
	}

	p->newFrame(); // Poll joysticks etc

	getInpOkay = appProcessKeyboardInput();

	tick();

	GameInp* pgi = gameInput;
	for (unsigned int i = 0; i < gameInpCount; i++, pgi++) {
		if (pgi->Input.pVal == NULL) {
			continue;
		}

		switch (pgi->nInput) {
			case 0:									// Undefined
				pgi->Input.nVal = 0;
				break;
			case GIT_CONSTANT:						// Constant value
				pgi->Input.nVal = pgi->Input.Constant.nConst;
				if (copy) {
					*(pgi->Input.pVal) = pgi->Input.nVal;
				}
				break;
			case GIT_SWITCH: {						// Digital input
				int s = getInpState(pgi->Input.Switch.nCode);

				if (pgi->nType & BIT_GROUP_ANALOG) {
					// Set analog controls to full
					if (s) {
						pgi->Input.nVal = 0xFFFF;
					} else {
						pgi->Input.nVal = 0x0001;
					}
					if (copy) {
						*(pgi->Input.pShortVal) = pgi->Input.nVal;
					}
				} else {
					// Binary controls
					if (s) {
						pgi->Input.nVal = 1;
					} else {
						pgi->Input.nVal = 0;
					}
					if (copy) {
						*(pgi->Input.pVal) = pgi->Input.nVal;
					}
				}

				break;
			}
			case GIT_KEYSLIDER:						// Keyboard slider
			case GIT_JOYSLIDER:	{					// Joystick slider
				int slider = pgi->Input.Slider.nSliderValue;
				if (pgi->nType == BIT_ANALOG_REL) {
					slider -= 0x8000;
					slider >>= 4;
				}

				pgi->Input.nVal = (unsigned short)slider;
				if (copy) {
					*(pgi->Input.pShortVal) = pgi->Input.nVal;
				}
				break;
			}
			case GIT_MOUSEAXIS:						// Mouse axis
				pgi->Input.nVal = (unsigned short)(getInpMouseAxis(pgi->Input.MouseAxis.nMouse, pgi->Input.MouseAxis.nAxis) * analogSpeed);
				if (copy) {
					*(pgi->Input.pShortVal) = pgi->Input.nVal;
				}
				break;
			case GIT_JOYAXIS_FULL:	{				// Joystick axis
				int joy = getInpGamepadAxis(pgi->Input.JoyAxis.nJoy, pgi->Input.JoyAxis.nAxis);

				if (pgi->nType == BIT_ANALOG_REL) {
					joy *= analogSpeed;
					joy >>= 13;

					// Clip axis to 8 bits
					if (joy < -32768) {
						joy = -32768;
					}
					if (joy > 32767) {
						joy = 32767;
					}
				} else {
					joy >>= 1;
					joy += 0x8000;

					// Clip axis to 16 bits
					if (joy < 0x0001) {
						joy = 0x0001;
					}
					if (joy > 0xFFFF) {
						joy = 0xFFFF;
					}
				}

				pgi->Input.nVal = (unsigned short)joy;
				if (copy) {
					*(pgi->Input.pShortVal) = pgi->Input.nVal;
				}

				break;
			}
			case GIT_JOYAXIS_NEG:	{				// Joystick axis Lo
				int joy = getInpGamepadAxis(pgi->Input.JoyAxis.nJoy, pgi->Input.JoyAxis.nAxis);
				if (joy < 32767) {
					joy = -joy;

					if (joy < 0x0000) {
						joy = 0x0000;
					}
					if (joy > 0xFFFF) {
						joy = 0xFFFF;
					}

					pgi->Input.nVal = (unsigned short)joy;
				} else {
					pgi->Input.nVal = 0;
				}

				if (copy) {
					*(pgi->Input.pShortVal) = pgi->Input.nVal;
				}
				break;
			}
			case GIT_JOYAXIS_POS:	{				// Joystick axis Hi
				int joy = getInpGamepadAxis(pgi->Input.JoyAxis.nJoy, pgi->Input.JoyAxis.nAxis);
				if (joy > 32767) {
					if (joy < 0x0000) {
						joy = 0x0000;
					}
					if (joy > 0xFFFF) {
						joy = 0xFFFF;
					}

					pgi->Input.nVal = (unsigned short)joy;
				} else {
					pgi->Input.nVal = 0;
				}

				if (copy) {
					*(pgi->Input.pShortVal) = pgi->Input.nVal;
				}
				break;
			}
		}
	}

	for (unsigned int i = 0; i < macroCount; i++, pgi++) {
		if (pgi->Macro.nMode) {						// Macro is defined
			if (copy && getInpState(pgi->Macro.Switch.nCode)) {
				for (int j = 0; j < 4; j++) {
					if (pgi->Macro.pVal[j]) {
						*(pgi->Macro.pVal[j]) = pgi->Macro.nVal[j];
					}
				}
			}
		}
	}

	return 0;
}

// Use this function as follows:
//  - First call with flags =  2
//  -  Then call with flags =  4 until all controls are released
//  -  Then call with flags =  8 until a control is activated
//  -  Then call with flags = 16 until all controls are released again
//     It will continue to return a control (reflecting the direction an axis is moved in)
//     Use this to handle analog controls correctly
//
// Call with flags & 1 to indicate that controls need to be polled (when normal input processing is disabled)
int InputInterface::find(int flags)
{
	if (!p) {
		return 1;
	}

	static int inputCode, delay, joyPrevPos;

	if (flags & 1) {
		p->newFrame();
	}

	int find = p->find(flags & 2);

	switch (flags) {
		case  4: {
			return find;
		}
		case  8: {
			if (find >= 0) {
				inputCode = find;
				if ((inputCode & 0x4000) && (inputCode & 0xFF) < 0x10) {
					joyPrevPos = getInpGamepadAxis((inputCode >> 8) & 0x3F, (inputCode >> 1) & 0x07);
				}
				delay = 0;
			}

			return find;
		}
		case 16: {
			// Treat joystick axes specially
			// Wait until the axis reports no movement for some time
			if ((inputCode & 0x4000) && (inputCode & 0xFF) < 0x10) {
				int joyPos = getInpGamepadAxis((inputCode >> 8) & 0x3F, (inputCode >> 1) & 0x07);
				int joyDelta = joyPrevPos - joyPos;

				joyPrevPos = joyPos;

				if (find != -1) {
					inputCode = find;
				}

				// While the movement is within the threshold, treat it as no movement
				if (joyDelta > -0x0100 || joyDelta < 0x0100) {
					delay++;
					if (delay > 64) {
						return -1;
					}
				} else {
					delay = 0;
				}

				return inputCode;
			}

			// Treat mouse axes specially
			// Wait until the axis reports no movement/movement in the same direction for some time
			if ((inputCode & 0x8000) && (inputCode & 0xFF) < 0x06) {
				int nMouseDelta = getInpMouseAxis((inputCode >> 8) & 0x3F, (inputCode >> 1) & 0x07);
				if (find == -1 || ((inputCode & 1) ? nMouseDelta > 0 : nMouseDelta < 0)) {
					delay++;
					if (delay > 128) {
						return -1;
					}
				} else {
					delay = 0;
					inputCode = find;
				}

				return inputCode;
			}

			return find;
		}
	}

	return -1;
}

// Get the name of a control and/or the device it's on (not all API's may support this)
// Either parameter can be passed as NULL
int InputInterface::getControlName(int code, wchar_t* deviceName, wchar_t* controlName)
{
	if (!inputOkay || !p) {
		return 1;
	}
	return p->getControlName(code, deviceName, controlName);
}

int InputInterface::select(const wchar_t* _driver)
{
	if (!_driver || !*_driver) {
		return 1;
	}

	wcscpy(inputSelect, _driver);
	return 0;
}

const wchar_t* InputInterface::getName()
{
	const wchar_t* pszName = NULL;

	pszName = inputSelect;
	if (pszName) {
		return pszName;
	}
	return localiseStringEx(IDS_ERR_UNKNOWN);
}

InterfaceInfo* InputInterface::get()
{
	if (IntInfoInit(&InpInfo)) {
		IntInfoFree(&InpInfo);
		return NULL;
	}

	if (inputOkay) {
		InpInfo.pszModuleName = getName();

	 	if (p) {
			p->get(&InpInfo);
		}

		for (int type = 0; type < 3; type++) {
			int deviceTypes[] = { 0x0000, 0x8000, 0x4000 };
			wchar_t deviceTypeNames[][16] = { L"keyboard", L"mouse   ", L"joystick" };
			wchar_t deviceName[MAX_PATH] = L"";
			int activeDevice = 0;

			while (activeDevice < 16 && p->getControlName(deviceTypes[type] | (activeDevice << 8), deviceName, NULL) == 0 && deviceName[0]) {
				wchar_t szString[MAX_PATH];
				_snwprintf(szString, sizearray(szString), L"%s %d %s", deviceTypeNames[type], activeDevice, deviceName);

				if (IntInfoAddStringInterface(&InpInfo, szString)) {
					break;
				}

				activeDevice++;
			}
		}
	} else {
		IntInfoAddStringInterface(&InpInfo, L"Input plugin not initialised");
	}

	return &InpInfo;
}
